﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;



namespace Effects.Effects.GuiParts
{
    /// <summary>
    ///  Simple slider  
    /// </summary>
    /// <remarks>
    ///  Simple control reacting on mouse move events. If the mouse button is pressed, slider change it's position according to
    ///  cursor coordinates.
    ///  It raises the event 
    /// </remarks>
    public partial class Slider : UserControl
    {
        #region constructor
        // construcotr stuff

        /// <summary>
        ///  Creates new slider.
        /// </summary>
        public Slider()
        {
            mouseDown = false;
            orientation = SliderOrientation.Horizontal;
            this.ForeColor = Color.White;

            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
            InitializeComponent();
            PrepareBackground();
            Render();
            Refresh();
        }

        #endregion

        #region properties

        #region orientation
        // orientation of the slider

        /// <summary>
        ///  Indicates orientation of the slider.
        /// </summary>
        private SliderOrientation orientation;

        /// <summary>
        ///  Gets or sets orientation of the slider.
        /// </summary>
        public SliderOrientation Orientation
        {
            get { return orientation; }
            set
            {
                orientation = value;
                PrepareBackground();
                Render();
                Refresh();
            }
        }

        #endregion

        #region min_value

        /// <summary>
        ///  Value when slider is fully left
        /// </summary>
        protected double minValue;

        /// <summary>
        ///  Gets or sets minimum value of the slider.
        /// </summary>
        public virtual double MinValue
        {
            get { return minValue; }
            set
            {
                minValue = value;
            }
        }

        #endregion

        #region max_value

        /// <summary>
        ///  Value when slider is fully right
        /// </summary>
        protected double maxValue;
        /// <summary>
        ///  Gets or sets maximum value of the slider.
        /// </summary>
        public virtual double MaxValue
        {
            get { return maxValue; }
            set
            {
                maxValue = value;
            }
        }

        #endregion

        #region value

        /// <summary>
        ///  Current value of the slider.
        /// </summary>
        protected double value;

        // when setting value postition of slider is changed
        // value is set from inside (not by user), so SliderPositionChanged event is NOT called
        /// <summary>
        ///  Gets or sets value of the slider. 
        /// </summary>
        /// <remarks>
        ///  When setting value postition of slider is changed. Setting this property means Value is set from inside (not by user), so SliderPositionChanged event is NOT called
        /// </remarks>
        public virtual double Value
        {
            get { return value; }
            set
            {
                // if mouseDown then user is setting the value
                if (!mouseDown)
                {
                    if (value != this.value)
                    {
                        this.value = value;
                        double part = ((double)(this.value - minValue)) / ((double)(maxValue - minValue));
                        if (orientation == SliderOrientation.Horizontal)
                            this.xPosition = Convert.ToInt32(part * this.Width_5);
                        else
                            this.xPosition = Convert.ToInt32(part * this.Height_5);
                        Render();
                        RefreshMultiThread();

                        if (SliderPositionChanged != null)
                            SliderPositionChanged(this, new SliderPositionChangedEventArgs(xPosition, value));
                    }
                }
            }
        }

        /// <summary>
        ///  Sets value to the slider.
        /// </summary>
        /// <param name="value"> Value </param>
        public void SetValue(double value)
        {
            if (value >= minValue && value <= maxValue)
            {
                this.value = value;
                double part = ((double)(this.value - minValue)) / ((double)(maxValue - minValue));
                if(orientation == SliderOrientation.Horizontal)
                    this.xPosition = Convert.ToInt32(part * this.Width_5);
                else
                    this.xPosition = Convert.ToInt32((1F-part) * this.Height_5);
                Render();
                RefreshMultiThread();
            }
        }

        #endregion

        #region xPosition

        /// <summary>
        ///  Current postition of the slider (x-coordinate)
        /// </summary>
        protected int xPosition;

        /// <summary>
        ///  Gets or sets position of the slider. 
        /// </summary>
        /// <remarks>
        ///  Settings this property means setting x-coordinate of the slider. It is assumed that this change was nade by user so SliderPositionChanged event is raised.
        /// </remarks>
        public int Xposition
        {
            get { return xPosition; }
            set
            {
                xPosition = value;
                double part;
                if (orientation == SliderOrientation.Horizontal)
                    part = ((double)xPosition) / ((double)Width_5);
                else
                    part = 1- ((double)xPosition) / ((double)Height_5);
                double newValue = (maxValue - minValue) * part + minValue;

                // event is raised only when new value is different from the old one
                if (newValue != this.value)
                {
                    this.value = newValue;
                    // calling delegate
                    try
                    {
                        this.SliderPositionChanged(this, new SliderPositionChangedEventArgs(xPosition, this.value));
                    }
                    catch (NullReferenceException )
                    { }
                    Render();
                    Refresh();
                }
            }
        }

        /// <summary>
        ///  Gets or sets postition of the slider.
        /// </summary>
        /// <remarks>
        ///  This property has same behaviour as Xposition, but it does NOT raises SliderPositionChanged event.
        /// </remarks>
        protected int XpostitionPreview
        {
            get { return xPosition; }
            set
            {
                xPosition = value;
                double part;
                if (orientation == SliderOrientation.Horizontal)
                    part = ((double)xPosition) / ((double)Width);
                else
                    part = ((double)xPosition) / ((double)Height);
                this.value = Convert.ToInt32((maxValue - minValue) * part) + minValue;
                Render();
                Refresh();
            }
        }

        #endregion

        #region foreground color

        /// <summary>
        ///  Color of the slider knob.
        /// </summary>
        private Color foreColor;

        /// <summary>
        ///  Brush that paints the knob
        /// </summary>
        private Brush foreBrush;

        /// <summary>
        ///  Gets or sets the foreground color of the slider.
        /// </summary>
        public Color ForeColor
        {
            get { return foreColor; }
            set
            {
                foreColor = value;
                foreBrush = new SolidBrush(foreColor);
            }
        }

        #endregion

        #endregion
       
        #region painting
        // rendering and painting the look

        /// <summary>
        ///  Bitmap representing background of the slider.
        /// </summary>
        protected Bitmap background;

        /// <summary>
        ///  Prepares background of the slider
        /// </summary>
        protected virtual void PrepareBackground()
        {
            Bitmap newBack = null;
            try
            {
                newBack = new Bitmap(this.Width, this.Height);
            }
            catch (ArgumentException)
            {
                newBack = new Bitmap(20, 20);
            }
            Graphics g = Graphics.FromImage(newBack);

            g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, this.Width, this.Height);
            if (orientation == SliderOrientation.Horizontal)
                g.DrawLine(Pens.Black, 0, this.Height / 2, this.Width, this.Height / 2);
            else
                g.DrawLine(Pens.Black, this.Width / 2, 0, this.Width / 2, this.Height);
            background = newBack;
        }

        /// <summary>
        ///  Image used for double buffer
        /// </summary>
        protected Bitmap image;

        /// <summary>
        ///  Renders image.
        /// </summary>
        protected virtual void Render()
        {
            Bitmap newImage = null;
            try
            {
                newImage = new Bitmap(Width, Height);
            }
            catch (ArgumentException)
            {
                newImage = new Bitmap(20, 20);
            }
            Graphics g = Graphics.FromImage(newImage);

            // paint background
            g.DrawImage(background, 0, 0);


            // paint slider
            if (this.Enabled)
            {
                if (orientation == SliderOrientation.Horizontal)
                {
                    g.FillRectangle(foreBrush, xPosition, 0, 5, this.Height);
                    g.DrawLine(Pens.Black, xPosition, 0, xPosition, Height);
                    g.DrawLine(Pens.Black, xPosition + 5, 0, xPosition + 5, Height);
                    g.DrawLine(Pens.Black, xPosition, 0, xPosition + 5, 0);
                    g.DrawLine(Pens.Black, xPosition + 5, Height - 1, xPosition + 5, Height-1);
                }
                else
                {
                    g.FillRectangle(foreBrush, 0, xPosition, this.Width, 5);
                    g.DrawLine(Pens.Black, 0,xPosition, Width, xPosition);
                    g.DrawLine(Pens.Black, 0, xPosition+5, Width, xPosition+5);
                    g.DrawLine(Pens.Black, 0, xPosition , 0, xPosition + 5);
                    g.DrawLine(Pens.Black, Width - 1 , xPosition , Width - 1, xPosition + 5);
                }
            }

            image = newImage;
        }

        /// <summary>
        ///  Overridden OnPaint method. Shows rendered image.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            //base.OnPaint(e);
            Graphics g = e.Graphics;
            g.DrawImage(image, 0, 0);
        }

        /// <summary>
        ///  Redraws slider. It prepares new background renders and refreshes slider.
        /// </summary>
        public void Redraw()
        {
            this.PrepareBackground();
            this.Render();
            this.RefreshMultiThread();
        }

        /// <summary>
        ///  Refreshes the control. Usable from all threads.
        /// </summary>
        private void RefreshMultiThread()
        {
            if (InvokeRequired)
                BeginInvoke(new MethodInvoker(this.Refresh));
            else
                Refresh();
        }

        /// <summary>
        ///  Current Height minus 5 pixels.
        /// </summary>
        private int Height_5;

        /// <summary>
        ///  Current Width minus 5 pixels.
        /// </summary>
        private int Width_5;

        /// <summary>
        /// Overridden OnResize method, it prepares new background and force redraw
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            Height_5 = Height - 6;
            Width_5 = Width - 6;
            PrepareBackground();
            Render();
            Refresh();
        }

        #endregion

        #region mouse_events
        // handling actions of the user

        /// <summary>
        ///  bool value indicates that mouse button is down on the control
        /// </summary>
        protected bool mouseDown;

        /// <summary>
        ///  Sets mouseDown true
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            //base.OnMouseDown(e);

            if (this.Enabled)
                mouseDown = true;
        }
        
        /// <summary>
        ///  If mouse button is pressed changes the value of the <see cref="Slider"/>.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            //base.OnMouseMove(e);
            if (mouseDown)
            {
                if (orientation == SliderOrientation.Horizontal)
                {
                    if (e.Location.X > this.Width_5)
                        Xposition = this.Width_5;
                    else if (e.Location.X < 0)
                        Xposition = 0;
                    else
                        Xposition = e.Location.X;
                }
                if (orientation == SliderOrientation.Vertical)
                {
                    if (e.Location.Y > this.Height_5)
                        Xposition = this.Height_5;
                    else if (e.Location.Y < 0)
                        Xposition = 0;
                    else
                        Xposition = e.Location.Y;
                }
            }
        }

        /// <summary>
        ///  Sets mouseDown false and sets slider position.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            //base.OnMouseUp(e);
            mouseDown = false;
        }

        /// <summary>
        ///  Resets slider. Sets value to the middle.
        /// </summary>
        public void Reset()
        {
            Value = (maxValue - minValue) / 2F;
        }

        #endregion

        /// <summary>
        ///  Event raised when position of <see cref="Slider"/> is changed by user.
        /// </summary>
        public event SliderPositionChanged SliderPositionChanged;
    }

    /// <summary>
    ///  Enumeration of slider orientations.
    /// </summary>
    public enum SliderOrientation
    {
        /// <summary>
        ///  Horizontal orientation - from left to right.
        /// </summary>
        Horizontal, 
        
        /// <summary>
        ///  Vertical orientation - from bottom to top.
        /// </summary>
        Vertical
    }

    /// <summary>
    ///  Delegate for SliderPositionChanged eveny
    ///  </summary>
    /// <param name="sender"> Sender of the event </param>
    /// <param name="e"> Event arguments </param>
    public delegate void SliderPositionChanged(object sender, SliderPositionChangedEventArgs e);

    // class representing SliderPositionChanged event arguments
    /// <summary>
    ///  Event arguments for SliderPositionChanged evetn: <see cref="Slider.SliderPositionChanged"/>
    /// </summary>
    public class SliderPositionChangedEventArgs: EventArgs
    {
        /// <summary>
        ///  postition of slider when event has been raised.
        /// </summary>
        private int position;
        /// <summary>
        ///  Gets position of the slider.
        /// </summary>
        public int Position
        {
            get{return position;}
        }

        /// <summary>
        ///  Value of slider when event has been raised.
        /// </summary>
        private double value;

        /// <summary>
        ///  Gets value of the slider.
        /// </summary>
        public double Value
        {
            get {return value;}
        }

        /// <summary>
        ///  Creates new SliderPositionEventArgs with given position and value.
        /// </summary>
        /// <param name="position"> Position of the slider </param>
        /// <param name="value"> Value of the slider </param>
        public SliderPositionChangedEventArgs(int position,double value)
        {
            this.position=position;
            this.value=value;
        }
    }
}
